package cate.game.db;

import cate.game.role.RolePart;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class MongoCache {

    // Map<repoHashCode, Map<roleUid, Item>>
    private Map<Integer, Map<String, Item>> cacheMap = new ConcurrentHashMap<>();

    public Optional findById(MongoRepository repo, String uid) {
        if(flushAllIng) { return Optional.empty(); }
        final Integer k = toKey(repo);
        Map<String, Item> subMap = cacheMap.get(k);
        if(subMap == null) {
            return repo.findById(uid);
        }
        Item item;
        synchronized (subMap) {
            item = subMap.get(uid);
        }
        if(item == null) {
            return repo.findById(uid);
        }
        return Optional.of(item.part);
    }

    public void save(MongoRepository repo, RolePart part) {
        if(flushAllIng) { return; }
        Item item = new Item();
        item.repo = repo;
        item.part = part;
        final Integer k = toKey(repo);
        Map<String, Item> subMap = cacheMap.get(k);
        if(subMap == null) {
            subMap = new LinkedHashMap<>();
            cacheMap.put(k, subMap);
        }
        synchronized (subMap) {
            subMap.put(part.uid, item);
        }
    }

    private static Integer toKey(MongoRepository repo) {
        return repo.hashCode();
    }

    /** 缓存数据项 */
    private static final class Item {
        private MongoRepository repo;
        private RolePart part;
        public void flush() {
            try {
                log.debug("【临时调试MongoCache】 保存了 {} @ {}", this.part.getClass(), this.part.getRole());
                this.repo.save(this.part);
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    /** 刷入DB少许数据 */
    public void flushFew() {
        if(flushAllIng) { return; }
        flush(16);
    }

    /**
     * @param limitNum 储存限制，<=0 即没有限制
     * */
    private void flush(int limitNum) {
        Iterator<Map.Entry<Integer, Map<String, Item>>> iter = cacheMap.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry<Integer, Map<String, Item>> entry = iter.next();
            Integer k = entry.getKey();
            Map<String, Item> subMap = entry.getValue();
            if(subMap != null) {
                List<Item> itemList = new ArrayList<>();
                synchronized (subMap) {
                    if(limitNum > 0) {
                        subMap.values().stream().limit(limitNum).forEach(item -> itemList.add(item));
                    } else {
                        subMap.values().stream().forEach(item -> itemList.add(item));
                    }
                }
                if(itemList.isEmpty()) {
                    log.debug("【临时调试MongoCache】itemList.isEmpty()");
                    continue;
                }
                log.debug("【临时调试MongoCache】待写入的itemList.size()={}", itemList.size());

                synchronized (subMap) {
                    for (Item item : itemList) {
                        subMap.remove(item.part.uid);
                    }
                    if(subMap.isEmpty()) {
                        try {
                            cacheMap.remove(k);
                        } catch (Exception e) {
                            log.error("", e);
                        }
                    }
                }

                long costBeg = System.currentTimeMillis();
                for(Item item : itemList) {
                    item.flush();
                }
                log.debug("【临时调试MongoCache】itemList flush 耗时{}ms", (System.currentTimeMillis()-costBeg));

            } else {
                log.debug("【临时调试MongoCache】k={}的subMap=null", k);
            }
        }
    }

    private volatile boolean flushAllIng = false;
    public void flushAll() {
        this.flushAllIng = true;
        flush(-1);
        this.flushAllIng = false;
    }

}
